feat(rails): support for solid queue#2942
Conversation
ba6dae4 to
0531f06
Compare
396f231 to
22e181a
Compare
cafb0e5 to
d61680a
Compare
22e181a to
ad38485
Compare
826f69d to
1c0f001
Compare
6322da1 to
edaee65
Compare
edaee65 to
42e895a
Compare
3eb29b0 to
6a62844
Compare
42e895a to
f1da26b
Compare
c9da666 to
d072fd5
Compare
951fdcb to
d9baea2
Compare
Sets messaging.message.id, messaging.destination.name, messaging.message.retry.count, and messaging.message.receive.latency on the consumer transaction, mirroring sentry-sidekiq's middleware. Adds an opt-in shared example that adapters can include to verify the data fields are populated correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps ActiveJob enqueue with a `queue.publish` child span when an active parent transaction exists, mirroring sentry-sidekiq's client middleware. Uses the public `around_enqueue` callback so no new ActiveJob monkey-patching is introduced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the OpenTelemetry pattern (the only documented way to add metadata to an ActiveJob payload — Rails has no public extension hook for serialize/deserialize): prepends the existing ActiveJobExtensions module with serialize/deserialize overrides that inject and recover sentry-trace and baggage headers under a namespaced "_sentry" key, wrapped in rescue blocks so a Sentry bug never breaks job execution. Threads the deserialized headers into SentryReporter.record, which now uses Sentry.continue_trace when present so the consumer transaction shares the producer's trace_id and chains under the producer queue.publish span. Guards the around_enqueue producer-span registration against duplicate registration (each Test::Application.define re-runs the railtie and without idempotency this stacks dozens of nested queue.publish spans). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er spec The producer-span change makes ActiveStorage's internally-enqueued AnalyzeJob emit an extra queue.publish span on the request transaction, which the previous index-based span lookups did not anticipate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The harness embedded :test-adapter specifics — the Rails 5.2 payload- preservation shim, the drain loop, and the enqueued-payload accessor. It also reached past ActiveJob::TestHelper to set ActiveJob::Base.queue_adapter directly, which conflicts with TestHelper's own _test_adapter slot (TestHelper's before_setup runs outside our around hook, so any direct assignment is silently shadowed). Switch the harness to ActiveJob's official queue_adapter_for_test hook and a small set of abstract methods (queue_adapter_for_test, with_adapter_active, drain, last_enqueued_payload, boot_adapter, reset_adapter) that adapter contexts implement. The :test-adapter shared context now owns everything specific to TestAdapter — including the Rails52FullPayloadTestAdapter shim and the drain loop. Subsequent adapter backends (e.g. Sidekiq) can compose with the harness without fighting it. Generalises the one shared-example line that reached into the TestAdapter shape (trace_propagation) via last_enqueued_payload. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…adapter Runs the common ActiveJob spec suite end-to-end against ActiveJob::QueueAdapters::SidekiqAdapter, driven by Sidekiq::Testing.fake! (block form, public API) and Sidekiq::Job.drain_all. Validates that the AJ tracing extension works as a generic, adapter- agnostic instrumentation — independent of sentry-sidekiq's native middleware. The :sidekiq context plugs into the harness via queue_adapter_for_test (installing a SidekiqAdapter instance through ActiveJob::TestHelper) and with_adapter_active (wrapping example.run in Sidekiq::Testing.fake! so fake mode is scoped per-example without touching global state). The context deliberately does not load sentry-sidekiq: loading it would install Sidekiq's client/server middleware globally and register SidekiqAdapter in skippable_job_adapters, both of which would short-circuit the AJ extension we're exercising. Sidekiq becomes a sentry-rails dev dependency, gated on Rails version (Sidekiq 7+ doesn't support Rails 5.2). The spec file and support file no-op cleanly on older matrices where the gem isn't bundled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drives the svelte-mini app to click a new "Trigger Job" button, which fetches POST /jobs/sample on the rails-mini app. The browser SDK propagates sentry-trace + baggage to the Rails request; the AJ extension this branch adds emits a queue.publish span on the http.server transaction at enqueue, and a queue.active_job consumer transaction when the :async pool runs the job. The spec asserts all three rails-side artifacts share one trace and are correctly linked (sentry-trace header on the controller request, parent_span_id on the consumer transaction, and matching messaging.* data on the producer and consumer ends). Polls the shared envelope log because :async runs the job on a separate thread, so the HTTP response returns before the consumer transaction is recorded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The harness was calling make_basic_app in its around-each block, which creates a fresh Rails::Application subclass and runs every initializer on each example. With 98 AJ examples that overhead dwarfed the actual test work — and worse, it left behind state (Sidekiq's @config_blocks list, accumulated routes, lingering Rails::Application subclasses) that made each subsequent make_basic_app a little slower. Under Ruby 3.4 + Rails 8.1.3 the per-example time grew 3× over the run, pushing the full sentry-rails CI past the 15-min timeout. Hoist make_basic_app to before(:all) and replicate the per-example bits of Sentry::Rails::Railtie's after_initialize hook in the around block — re-init Sentry, re-activate tracing / structured logging, re-register the AJ event handlers. The one-time extensions (controller methods, streaming reporter, backtrace cleanup, etc.) were already installed by the initial make_basic_app and persist for the group. Also memoize the SidekiqAdapter instance in the :sidekiq context. Each SidekiqAdapter.new appended to Sidekiq's internal @config_blocks list and added an on(:quiet) callback; creating a fresh adapter per example was unnecessary global churn. Result: spec/active_job goes from 33s → 0.8s, and the full sentry-rails spec task (Ruby 3.4 + Rails 8.1.3) goes from 9:22 to 2:31 — well under the CI limit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scope reads allowed user keys as symbols, so we gotta symbolize users that we receive as plain data from the job payload.
d65c086 to
49aa99c
Compare
d9baea2 to
f2c6e34
Compare
0afc95a to
c0bc241
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c0bc241. Configure here.
| ready = SolidQueue::ReadyExecution.claim("*", 100, process.id) | ||
| break if ready.empty? && SolidQueue::ScheduledExecution.none? | ||
| ready.each(&:perform) | ||
| end |
There was a problem hiding this comment.
Drain loop never terminates
Medium Severity
The drain loop only exits when the ready queue is empty and there are no rows in SolidQueue::ScheduledExecution. If scheduled rows exist but none are due yet (scheduled_at still in the future), dispatch_next_batch promotes nothing, claim returns an empty set, and the loop keeps spinning because scheduled rows remain.
Reviewed by Cursor Bugbot for commit c0bc241. Configure here.
| SolidQueue::ScheduledExecution.dispatch_next_batch(100) | ||
| ready = SolidQueue::ReadyExecution.claim("*", 100, process.id) | ||
| break if ready.empty? && SolidQueue::ScheduledExecution.none? | ||
| ready.each(&:perform) |
There was a problem hiding this comment.
Drain swallows job exceptions
High Severity
Calling perform on claimed Solid Queue executions returns a result object and rescues job failures instead of re-raising. The shared ActiveJob harness examples expect drain to propagate the same exceptions as ActiveJob::Base.execute, including for expect { drain }.to raise_error cases.
Reviewed by Cursor Bugbot for commit c0bc241. Configure here.
8ed5c5b to
5360bcf
Compare


This adds support for
SolideQueueby simply adding its spec suite that makes sure our common ActiveJob integration works withSolidQueue, including distributed tracing.We may follow up with SQ-specific features added in separate PR(s).
Closes #2587